package com.manlost.web.common.cookie;


import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

/**
 * ManLostCookie管理器
 * 
 * @author kang.zhouk
 *
 */
public class ManLostCookieManger  {
	public  static final String MANLOST_COOKIE_MANGER 				  = "_manlost_cookie_manager_";
	
    private static final MLostCookieHelpers    cookieHelpers          = new MLostCookieHelpers();
    private static final MLostCookieMapHelpers cookieMapHelpers       = new MLostCookieMapHelpers();
    
    private HttpServletRequest                 request                = null;
    private HttpServletResponse                response               = null;


    
    static {
       
    	// bulid cookieName
        Field[] fields = MLostCookieNameEnum.class.getFields();
        for (Field field : fields) {
            MLostCookieNamePolicy mlostcookieNamePolicy = field.getAnnotation(MLostCookieNamePolicy.class);
            if (mlostcookieNamePolicy == null) {
                throw new IllegalArgumentException("Enum const '" + field.getName() + "' must need annotation : "
                                                   + MLostCookieNamePolicy.class.getName());
            }
            MLostCookieHelper mlHelper = cookieHelpers.buildMLostCookieHelper(mlostcookieNamePolicy);
            if (mlHelper != null) {
            	// key:cookie名  value：cookie具有的属性
                cookieHelpers.addMLostCookieNameHelperPair(MLostCookieNameEnum.valueOf(field.getName()), mlHelper);
            }
        }
 
        // FIXME bulid cookieKey
        fields = MLostCookieKeyEnum.class.getFields();
        for (Field f : fields) {
            MLostCookieKeyPolicy keyPolicy = f.getAnnotation(MLostCookieKeyPolicy.class);
            if (keyPolicy == null) {
                throw new IllegalArgumentException("Enum const '" + f.getName() + "' must need annotation : "
                                                   + MLostCookieKeyPolicy.class.getName());
            }
  
            MLostCookieKeyEnum key = MLostCookieKeyEnum.valueOf(f.getName());
            MLostCookieMapHelper mlostMapHelper = cookieMapHelpers.buildMLostCookieMap(keyPolicy);
            if (mlostMapHelper != null) {
                Set<MLostCookieNameEnum> names = mlostMapHelper.getWithinNames();
                for (MLostCookieNameEnum name : names) {
                    cookieHelpers.addMLostCookieKey(name, key);
                }
                cookieMapHelpers.addMLostCookieKeyMapHelperPair(key, mlostMapHelper);
            }
        }
    }

    public ManLostCookieManger(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
    }

    /**
     * 获取cookie值
     * 
     * @param name
     * @param key
     * @return
     */
    public String getCookie(MLostCookieNameEnum cookieName, MLostCookieKeyEnum cookieKey) {
        if (cookieName == null || cookieKey == null) {
            return null;
        }
       
        Map<MLostCookieKeyEnum, String> cookieValueMap = loadCookie(cookieName, null);
        if (cookieValueMap != null) {
            return cookieValueMap.get(cookieKey);
        }
       
        return null;
    }

    /**
     * 设置cookie值
     * 
     * @param name
     * @param key
     * @param value
     */
    public void setCookie(MLostCookieNameEnum cookieName, MLostCookieKeyEnum cookieKey, String cookieValue) {
        if (cookieName == null || cookieKey == null) {
            return;
        }
        
        // if key is not in name
        if (!cookieMapHelpers.isWithinNames(cookieKey, cookieName)) {
            throw new IllegalArgumentException("Can't set cookie key '" + cookieKey
                                               + "', please define it for the cookie [name=" + cookieName + "]");
        }
        
        // if no cookie exist, return empty map
        Map<MLostCookieKeyEnum, String> cookieValueMap = loadCookie(cookieName, null);
        
        if (cookieValueMap == null) {
        	return ;
        }
       
        if (StringUtils.isNotEmpty(cookieValue)) {
            // put or update the map
        	cookieValueMap.put(cookieKey, cookieValue);
        } else {
        	cookieValueMap.remove(cookieKey);
        }
        
        // just set modified flag
        request.setAttribute(cookieName.getValue() + MLostCookieConstant.COOKIE_MODIFIED_FLAG_SUFFIX, new Boolean(true));
    }

    /**
     * 清除所有cookie
     * 
     */
    public void clearAllCookie() {
        for (MLostCookieNameEnum name : MLostCookieNameEnum.values()) {
            clearCookie(name);
        }
    }

    public void clearCookie(MLostCookieNameEnum name) {
        Set<MLostCookieKeyEnum> keys = cookieHelpers.getMLostCookieKeySet(name);
        if (keys != null) {
            if (keys.isEmpty()) {
                setCookie(name, null, null);
            } else {
                for (MLostCookieKeyEnum key : keys) {
                    setCookie(name, key, null);
                }
            }
        }
    }

    /**
     * 保存所有cookie
     */
    public void saveAllCookie() {
        for (MLostCookieNameEnum name : MLostCookieNameEnum.values()) {
            saveCookie(name);
        }
    }

    public void saveCookie(MLostCookieNameEnum name) {
        saveCookie(name, "");
    }

    @SuppressWarnings("unchecked")
    private void saveCookie(MLostCookieNameEnum cookieName, String defaultValue) {
        if (cookieName == null) {
            return;
        }
        
        if (request.getAttribute(cookieName.getValue() + MLostCookieConstant.COOKIE_MODIFIED_FLAG_SUFFIX) != null) {
            String cookieValue = defaultValue;
            
            Map<MLostCookieKeyEnum, String> cookieValueMap = (Map<MLostCookieKeyEnum, String>) request.getAttribute(cookieName.getValue());
            if (cookieValueMap != null) {
            	cookieValue = cookieMapToStrValue(cookieValueMap, cookieValue);
            }
            
            MLostCookieHelper cookieHelper = cookieHelpers.getMLostCookieHelper(cookieName);
            if (cookieHelper != null) {
            	response.addHeader("Set-Cookie", CookieBuilder.getCookieHeaderValue(cookieName.getValue(),
            					cookieValue, cookieHelper.getPath(),
            					//cookieHelper.getDomain(), cookieHelper.getMaxAge(),
            					null, StringUtils.isBlank(cookieValue) ? 0 : cookieHelper.getMaxAge(),
            					false, true));
   
            }
            request.setAttribute(cookieName.getValue() + MLostCookieConstant.COOKIE_MODIFIED_FLAG_SUFFIX, null);
        }
    }

    /**
     * parse cookie
     * 
     * @param name
     * @param defaultValue
     * @return
     */
    @SuppressWarnings("unchecked")
    private Map<MLostCookieKeyEnum, String> loadCookie(MLostCookieNameEnum cookieName, String defaultValue) {
        if (cookieName == null) {
            return null;
        }
        
        // cache first
        Map<MLostCookieKeyEnum, String> cookieValueMap = (Map<MLostCookieKeyEnum, String>) request.getAttribute(cookieName.getValue());
        if (cookieValueMap != null) {
            return cookieValueMap;
        }
        
        // otherwise first load
        String value = cookieHelpers.getServletCookieValue(request.getCookies(), cookieName, defaultValue);
        cookieValueMap = strValueToCookieMap(value);

        request.setAttribute(cookieName.getValue(), cookieValueMap);
        return cookieValueMap;
    }

    /**
     * cookieValueString 转换成 cookieValueMap
     * 
     * @param String
     * @return cookieValueMap
     */
    private Map<MLostCookieKeyEnum, String> strValueToCookieMap(String cookieValue) {
        Map<MLostCookieKeyEnum, String> cookieValueMap = new HashMap<MLostCookieKeyEnum, String>();
        if (StringUtils.isEmpty(cookieValue)) {
            return cookieValueMap;
        }

        String[] cookiePairs = StringUtils.split(cookieValue, MLostCookieConstant.COOKIE_MAP_SEPARATOR_CHAR);

        if (cookiePairs != null) {
            for (String pair : cookiePairs) {
                int offset = pair.indexOf(MLostCookieConstant.COOKIE_KEY_VALUE_SEPARATOR_CHAR);
                if (offset > 0 && offset < pair.length()) {
                    MLostCookieKeyEnum key = MLostCookieKeyEnum.keyOf(pair.substring(0, offset));
                    if (key != null) {
                    	cookieValueMap.put(key, pair.substring(offset + 1, pair.length()));
                    }
                }
            }
        }
        return cookieValueMap;
    }

    /**
     * cookieValueMap 转化成 cookieValueString
     * 
     * @param cookieValueMap
     * @param defaultValue
     * @return cookieValueString
     */
    private String cookieMapToStrValue(Map<MLostCookieKeyEnum, String> cookieValueMap, String defaultValue) {
        StringBuilder cookieValue = new StringBuilder();
        if (cookieValue != null) {
            int i = 0;
            for (Map.Entry<MLostCookieKeyEnum, String> entry : cookieValueMap.entrySet()) {
                if (i != 0) {
                	cookieValue.append(MLostCookieConstant.COOKIE_MAP_SEPARATOR_CHAR);
                }
                cookieValue.append(entry.getKey().getValue() + MLostCookieConstant.COOKIE_KEY_VALUE_SEPARATOR_CHAR
                             + entry.getValue());
                i++;
            }
        }
        if (StringUtils.isEmpty(cookieValue.toString())) {
            return defaultValue;
        }

        return cookieValue.toString();
    }

    /**
     * 每一个cookie都有相应的CookieHelper
     */
    private static class MLostCookieHelpers {

        private final Map<MLostCookieNameEnum, MLostCookieHelper> mlostCookieNameHelperMap = new HashMap<MLostCookieNameEnum, MLostCookieHelper>();

        public void addMLostCookieNameHelperPair(MLostCookieNameEnum name, MLostCookieHelper mlostHelper) {
            if (name != null && mlostHelper != null) {
                mlostCookieNameHelperMap.put(name, mlostHelper);
            }
        }

        public MLostCookieHelper getMLostCookieHelper(MLostCookieNameEnum name) {
            return mlostCookieNameHelperMap.get(name);
        }

        /**
         * 为某个cookie build相应的CookieHelper
         * 
         * @param mlostPolicy
         * @return
         */
        public MLostCookieHelper buildMLostCookieHelper(MLostCookieNamePolicy mlostPolicy) {
            if (mlostPolicy != null) {
                MLostCookieHelper mlostHelper = new MLostCookieHelper();
                mlostHelper.setMaxAge(mlostPolicy.maxAge());
                mlostHelper.setPath(mlostPolicy.path().getValue());
                mlostHelper.setDomain(mlostPolicy.domain().getValue());
                mlostHelper.setEncrypt(mlostPolicy.isEncrypt());
                return mlostHelper;
            }
            return null;
        }

        public void addMLostCookieKey(MLostCookieNameEnum name, MLostCookieKeyEnum key) {
            MLostCookieHelper mlostHelper = mlostCookieNameHelperMap.get(name);
            addMLostCookieKey(mlostHelper, key);
        }

        public void addMLostCookieKey(MLostCookieHelper mlostHelper, MLostCookieKeyEnum key) {
            if (mlostHelper != null && key != null) {
                mlostHelper.addKey(key);
            }
        }

        
        public Set<MLostCookieKeyEnum> getMLostCookieKeySet(MLostCookieNameEnum name) {
            MLostCookieHelper mlostHelper = mlostCookieNameHelperMap.get(name);
            return getMLostCookieKey(mlostHelper);
        }

        private Set<MLostCookieKeyEnum> getMLostCookieKey(MLostCookieHelper mlostHelper) {
            if (mlostHelper != null) {
                return mlostHelper.getKeys();
            }
            return null;
        }

       
        public Cookie buildServletCookie(MLostCookieNameEnum cookieName, String value) {
            // get helper to create servlet cookie
            MLostCookieHelper mlostHelper = getMLostCookieHelper(cookieName);
            if (mlostHelper != null) {
                value = encryptValue(mlostHelper, value);
                Cookie c = new Cookie(cookieName.getValue(), value);
                c.setMaxAge(mlostHelper.getMaxAge());
                c.setPath(mlostHelper.getPath());
                c.setDomain(mlostHelper.getDomain());
                if (!checkValue(mlostHelper, c)) {
                    c.setMaxAge(0);
                }
                System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
                System.out.println(c.getMaxAge() + "-" + c.getName() + "-" + c.getDomain());
                return c;
            }
            return null;
        }

        /**
         * 枚举cookie获取相应的cookieValue
         * 
         * @param cookies
         * @param name
         * @param defaultValue
         * @return
         */
        public String getServletCookieValue(Cookie[] cookies, MLostCookieNameEnum name, String defaultValue) {
            String value = defaultValue;
            if (cookies != null) {
                for (Cookie c : cookies) {
                    if (name.getValue().equals(c.getName())) {
                        value = c.getValue();
                        break;
                    }
                }
            }
            return parseValue(name, value);
        }

        public boolean checkValue(MLostCookieHelper mlostHelper, Cookie cookie) {
            if (mlostHelper != null) {
                return mlostHelper.checkValue(cookie);
            }
            return false;
        }
        
        public String encryptValue(MLostCookieHelper mlostHelper, String value) {
            if (mlostHelper != null) {
                return mlostHelper.encryptValue(value);
            }
            return value;
        }

        private String parseValue(MLostCookieNameEnum name, String value) {
            MLostCookieHelper mlostHelper = mlostCookieNameHelperMap.get(name);
            String decryptValue = decryptValue(mlostHelper, value);
            return decryptValue;
        }

        private String decryptValue(MLostCookieHelper mlostHelper, String value) {
            if (mlostHelper != null) {
                return mlostHelper.decryptValue(value);
            }
            return value;
        }
    }

    /**
     * 定义一个cookie具有的属性
     */
    private static class MLostCookieHelper {
    	
        private int                   maxAge;
        private String                path;
        private String                domain;
        private boolean               isEncrypt;
        
        /**
         * 该cookie具有的key集合
         */
        private Set<MLostCookieKeyEnum> keys = new HashSet<MLostCookieKeyEnum>();

       
        public boolean checkValue(Cookie cookie) {
            if (cookie == null || StringUtils.isBlank(cookie.getValue())) {
                return false;
            }
            return true;
        }

       
        /**
         * 加密cookie TODO
         * 
         * @param value
         * @return
         */
        private String encryptValue(String value) {
            
            return value;
        }

        /**
         * 解密cookie TODO
         * 
         * @param value
         * @return
         */
        public String decryptValue(String value) {
            
            return value;
        }

        public int getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(int maxAge) {
            this.maxAge = maxAge;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getDomain() {
            return domain;
        }

        public void setDomain(String domain) {
            this.domain = domain;
        }

        public boolean isEncrypt() {
            return isEncrypt;
        }

        public void setEncrypt(boolean isEncrypt) {
            this.isEncrypt = isEncrypt;
        }

        public Set<MLostCookieKeyEnum> getKeys() {
            return keys;
        }

        public void addKey(MLostCookieKeyEnum key) {
            this.keys.add(key);
        }
    }

    private static class MLostCookieMapHelpers {

        private final Map<MLostCookieKeyEnum, MLostCookieMapHelper> mlostCookieKeyHelperMap = new HashMap<MLostCookieKeyEnum, MLostCookieMapHelper>();

        public void addMLostCookieKeyMapHelperPair(MLostCookieKeyEnum keyName, MLostCookieMapHelper acMapHelper) {
            if (keyName != null && acMapHelper != null) {
                mlostCookieKeyHelperMap.put(keyName, acMapHelper);
            }
        }
        
        /**
         * 
         * 
         * @param mlostPolicy
         * @return
         */
        public MLostCookieMapHelper buildMLostCookieMap(MLostCookieKeyPolicy mlostPolicy) {
            if (mlostPolicy != null) {
                MLostCookieMapHelper mlostMapHelper = new MLostCookieMapHelper();
                mlostMapHelper.setWithinNames(new HashSet<MLostCookieNameEnum>(Arrays.asList(mlostPolicy.withinNames())));
                return mlostMapHelper;
            }
            return null;
        }

        /**
         * 检测cookie是否包含指定key
         * 
         * @param keyName
         * @param name
         * @return
         */
        public boolean isWithinNames(MLostCookieKeyEnum keyName, MLostCookieNameEnum cookieName) {
            MLostCookieMapHelper cookieMapHelper = mlostCookieKeyHelperMap.get(keyName);
            return isWithinNames(cookieMapHelper, cookieName);
        }

        private boolean isWithinNames(MLostCookieMapHelper cookieMapHelper, MLostCookieNameEnum name) {
            if (cookieMapHelper != null) {
                return cookieMapHelper.isWithinNames(name);
            }
            return false;
        }

    }

  
    private static class MLostCookieMapHelper {

        private Set<MLostCookieNameEnum> withinNames;

        public Set<MLostCookieNameEnum> getWithinNames() {
            return withinNames;
        }

        public void setWithinNames(Set<MLostCookieNameEnum> withinNames) {
            this.withinNames = withinNames;
        }

        public boolean isWithinNames(MLostCookieNameEnum name) {
            if (withinNames != null) {
                return withinNames.contains(name);
            }
            return false;
        }
    }

   
    public static void main(String[] args) {
         for (MLostCookieNameEnum name : MLostCookieNameEnum.values()) {
         System.out.println(name + " has keys : ");
         Set<MLostCookieKeyEnum> keys = cookieHelpers.getMLostCookieKeySet(name);
         for (MLostCookieKeyEnum key : keys) {
         if (cookieMapHelpers.isWithinNames(key, name)) {
         System.out.println(key);
         } else {
         System.out.println("Unauthentic : " + key);
         }
        
         }
         System.out.println("-----------------------");
         }
    }

}

